Skip to content

Vendor avatar is derived via Gravatar; website_url must be http(s) - #317

Merged
vsilent merged 1 commit into
devfrom
fix/vendor-avatar-gravatar
Oct 30, 2026
Merged

vsilent merged 1 commit into
devfrom
fix/vendor-avatar-gravatar

Conversation

@vsilent

@vsilent vsilent commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

The marketplace vendor profile accepted a free-form avatar_url and the public author page rendered it verbatim in <img src> (src/routes/marketplace/creator.rs → GET /api/vendors/{slug} → blog /applications/marketplace/author/[id]). A vendor could put an arbitrary external image URL on a page other users visit (tracking pixels, IP/referrer leaks, mixed-content). website_url was likewise rendered raw into <a href> — javascript: there is XSS.

Changes

  • src/helpers/gravatar.rs (new): gravatar_url(email) = https://www.gravatar.com/avatar/{md5(trim(lower(email)))}?s=230&d=identicon&r=pg (same convention as the user service), plus is_gravatar_url / is_http_url guards. md-5 = "0.10" added to Cargo.toml.
  • PATCH /mine/vendor-profile: avatar_url removed from the request struct — the stored avatar is always derived from the authenticated user's email via Gravatar; client-supplied values are ignored (serde ignores unknown fields, old clients stay compatible). website_url must be http(s) or 400.
  • Migration 20261029120000_null_non_gravatar_avatars: NULLs every non-Gravatar avatar_url already stored (e.g. the optimum profile's external logo) — the next profile save fills in the derived Gravatar URL.
  • Tests: 2 updated assertions + 3 new (client avatar_url ignored incl. public endpoint, javascript:/data:/vbscript: website_url → 400). 4 unit tests for the helpers.

Admin vendor-profile handlers verified to carry no avatar_url/website_url fields — no change needed there.

Verification

  • SQLX_OFFLINE=true cargo build — OK
  • cargo nextest run -E 'binary(marketplace_creator_self_vendor_profile)' — 10 passed
  • cargo test --test marketplace_public_vendor_profile — 8 passed
  • cargo test --lib gravatar — 4 passed
  • cargo clippy — 0 issues in changed files (27 pre-existing lints in build.rs/td-audit/cli//routes/ from newer local clippy 1.94, identical on dev)

Notes

  • Gravatar MD5-hashes the email (mild privacy trade-off) — same as what the platform already does for every user photo (user/app/oauth2_server/views.py).
  • CSP img-src already allows gravatar.com; no nginx change needed.

The marketplace vendor profile accepted a free-form avatar_url and served
it verbatim on the public author page, so a vendor could put an arbitrary
external image URL on a page other users visit. avatar_url is now always
derived from the account email via Gravatar (d=identicon), the client
value is ignored, and a migration NULLs non-Gravatar values already
stored (the next profile save fills in the derived URL). website_url,
rendered into an <a href>, now rejects non-http(s) schemes.
@vsilent
vsilent merged commit a61bfa1 into dev Oct 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants